| 1 | import { Stack, router, useLocalSearchParams } from "expo-router"; |
| 2 | import { BellOff, Bell, LogOut, MoreHorizontal, Star, StarOff } from "lucide-react-native"; |
| 3 | import { useState } from "react"; |
| 4 | import { Alert, Pressable, View } from "react-native"; |
| 5 | |
| 6 | import { Conversation } from "@/components/chat/conversation"; |
| 7 | import { Sheet, SheetAction } from "@/components/chat/sheet"; |
| 8 | import { Empty, Loading, Screen, Text } from "@/components/ui"; |
| 9 | import { useChatActions, useChatSidebar } from "@/lib/chat"; |
| 10 | import { space, useColors } from "@/theme"; |
| 11 | |
| 12 | /** |
| 13 | * A conversation. `channel` is a channel's id, or a channel's name when a |
| 14 | * link named it (`/acme/-/chat/general`). The ⋯ button stars, mutes or |
| 15 | * leaves it. |
| 16 | */ |
| 17 | export default function ConversationScreen() { |
| 18 | const params = useLocalSearchParams<{ channel: string }>(); |
| 19 | const sidebar = useChatSidebar(); |
| 20 | const colors = useColors(); |
| 21 | const [menu, setMenu] = useState(false); |
| 22 | const entry = sidebar.data?.entries.find((one) => one.channel.id === params.channel || one.channel.name === params.channel); |
| 23 | // A link by name waits for the sidebar to say which channel it is. |
| 24 | const channelId = entry?.channel.id ?? (/^[a-z]+_/.test(params.channel) ? params.channel : ""); |
| 25 | const actions = useChatActions(channelId); |
| 26 | const dm = entry?.channel.kind === "dm"; |
| 27 | const title = entry ? (dm ? entry.title : `#${entry.channel.name}`) : ""; |
| 28 | |
| 29 | if (!channelId) { |
| 30 | return ( |
| 31 | <Screen> |
| 32 | <Stack.Screen options={{ title: "" }} /> |
| 33 | {sidebar.isPending ? <Loading /> : <Empty title="Not in this conversation" body="It may be private, or you may need to join it first." />} |
| 34 | </Screen> |
| 35 | ); |
| 36 | } |
| 37 | |
| 38 | return ( |
| 39 | <Screen> |
| 40 | <Stack.Screen |
| 41 | options={{ |
| 42 | headerTitle: () => ( |
| 43 | <View> |
| 44 | <Text style={{ fontWeight: "700" }} numberOfLines={1}> |
| 45 | {title} |
| 46 | </Text> |
| 47 | {entry?.channel.topic ? ( |
| 48 | <Text size="small" tone="muted" numberOfLines={1}> |
| 49 | {entry.channel.topic} |
| 50 | </Text> |
| 51 | ) : null} |
| 52 | </View> |
| 53 | ), |
| 54 | headerRight: () => |
| 55 | entry ? ( |
| 56 | <Pressable accessibilityLabel="Conversation options" hitSlop={10} onPress={() => setMenu(true)}> |
| 57 | <MoreHorizontal size={22} color={colors.fg} /> |
| 58 | </Pressable> |
| 59 | ) : null, |
| 60 | }} |
| 61 | /> |
| 62 | <Conversation channelId={channelId} thread={null} title={title || "this conversation"} /> |
| 63 | <Sheet visible={menu} onClose={() => setMenu(false)}> |
| 64 | <View style={{ paddingHorizontal: space.lg, paddingBottom: space.sm }}> |
| 65 | <Text size="heading">{title}</Text> |
| 66 | </View> |
| 67 | <SheetAction |
| 68 | icon={entry?.starred ? StarOff : Star} |
| 69 | label={entry?.starred ? "Unstar" : "Star"} |
| 70 | onPress={() => { |
| 71 | setMenu(false); |
| 72 | actions.preferences.mutate({ starred: !entry?.starred }); |
| 73 | }} |
| 74 | /> |
| 75 | <SheetAction |
| 76 | icon={entry?.muted ? Bell : BellOff} |
| 77 | label={entry?.muted ? "Unmute" : "Mute"} |
| 78 | onPress={() => { |
| 79 | setMenu(false); |
| 80 | actions.preferences.mutate({ muted: !entry?.muted }); |
| 81 | }} |
| 82 | /> |
| 83 | {!dm ? ( |
| 84 | <SheetAction |
| 85 | icon={LogOut} |
| 86 | label="Leave channel" |
| 87 | danger |
| 88 | onPress={() => { |
| 89 | setMenu(false); |
| 90 | Alert.alert(`Leave ${title}?`, "You can join it again from Browse.", [ |
| 91 | { text: "Cancel", style: "cancel" }, |
| 92 | { text: "Leave", style: "destructive", onPress: () => actions.leave.mutate(undefined, { onSuccess: () => router.back() }) }, |
| 93 | ]); |
| 94 | }} |
| 95 | /> |
| 96 | ) : null} |
| 97 | </Sheet> |
| 98 | </Screen> |
| 99 | ); |
| 100 | } |